-- MoonSaberTrader.lua

-- initialize saved variables as necessary

MoonSaberTrader_CharacterData = {}
MoonSaberTrader_SharedData = {}

local function expose_table(table, sub_table_name)
  if table[sub_table_name] == nil then
    table[sub_table_name] = {}
  end

  return table[sub_table_name]
end

--local inventory_cost_data
--local merchant_sell_data

local function setup_variables()
  --inventory_cost_data = expose_table(MoonSaberTrader_CharacterData, 'inventory_cost_data')
  --merchant_sell_data = expose_table(MoonSaberTrader_SharedData, 'merchant_sell_data')
end

-- initialize session variables

local event_handlers = {}

local listing_names = {}
local listing_index = {}
local listing_data = {}
listing_data['self_listings'] = {}
listing_data['total_listings'] = {}
listing_data['self_minbid'] = {}
listing_data['self_buyout'] = {}
listing_data['competition_minbid'] = {}
listing_data['competition_buyout'] = {}

-- magic constants

local MARKET_ROWS = 15

-- misc functions

local function format_price(input_price)
  return format('%.4f', input_price / 10000)
end

local function get_item_name_from_item_link(item_link)
  local i, j, item_name = string.find(item_link, '%|h(.*)%|%h')
  if item_name == nil then
    return
  end

  item_name = string.gsub(item_name, '%[', "")
  item_name = string.gsub(item_name, '%]', "")

  return item_name
end

local function update_counter(table, key, count)
  if count == nil then
    count = 1
  end

  local old_value = table[key]
  if old_value == nil then
    table[key] = count
  else
    table[key] = old_value + count
  end
end

local function update_min(table, key, value)
  local old_value = table[key]
  if old_value == nil then
    table[key] = value
  elseif value < old_value then
    table[key] = value
  end
end

-- auction house listings

local last_num_batch_auctions = nil
local last_total_auctions = nil
local repeat_count = 0

local function auction_item_list_update()
  local player_name = UnitName("player")

  -- variable names from World of Warcraft Programming, page 529
  local numBatchAuctions, totalAuctions = GetNumAuctionItems("list")
  if (numBatchAuctions == last_num_batch_auctions) and (totalAuctions == last_total_auctions) then
    repeat_count = repeat_count + 1
    --ChatFrame3:AddMessage(format('found %d/%d AH listings (%d times)', numBatchAuctions, totalAuctions, repeat_count))

    local repeat_cost = numBatchAuctions * repeat_count
    if repeat_cost > 60000 then
      ChatFrame3:AddMessage(format('aborting scan on repeat %d of %d/%d', repeat_count, numBatchAuctions, totalAuctions))
      return
    end
  else
    --ChatFrame3:AddMessage(format('found %d/%d AH listings', numBatchAuctions, totalAuctions))
    last_num_batch_auctions = numBatchAuctions
    last_total_auctions = totalAuctions
    repeat_count = 1
  end

  if numBatchAuctions == 0 then
    return
  end

  --DEFAULT_CHAT_FRAME:AddMessage(format('MoonSaberTrader AH query received %d/%d items', numBatchAuctions, totalAuctions))
  if numBatchAuctions < totalAuctions then
    return
  end

  local batch_index = {}
  for i = 1,totalAuctions do
    -- variable names from World of Warcraft Programming, page 447
    local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo("list", i)

    --DEFAULT_CHAT_FRAME:AddMessage(format('MoonSaberTrader AH[%d] "%s" x %d by %s', i, name, count, owner))

    if listing_index[name] == nil then
      table.insert(listing_names, name)
      listing_index[name] = #listing_names
    end

    local init_item = batch_index[name] == nil
    if init_item then
      batch_index[name] = listing_index[name]
    end
      
    local listing_id = batch_index[name]
    if init_item then
      listing_data['self_listings'][listing_id] = 0
      listing_data['total_listings'][listing_id] = 0
      listing_data['self_minbid'][listing_id] = nil
      listing_data['self_buyout'][listing_id] = nil
      listing_data['competition_minbid'][listing_id] = nil
      listing_data['competition_buyout'][listing_id] = nil
    end
    local listing_id = batch_index[name]
      
    update_counter(listing_data['total_listings'], listing_id)

    -- compare our bids against everyone else
    if owner == player_name then
      update_counter(listing_data['self_listings'], listing_id)
      update_min(listing_data['self_minbid'], listing_id, minBid / count)
    else
      update_min(listing_data['competition_minbid'], listing_id, minBid / count)
    end

    if (buyoutPrice ~= nil) and (buyoutPrice ~= 0) then
      if owner == player_name then
        update_min(listing_data['self_buyout'], listing_id, buyoutPrice / count)
      else
        update_min(listing_data['competition_buyout'], listing_id, buyoutPrice / count)
      end
    end
  end
end

local auction_owned_counts = {}
local function auction_owned_list_update()
  -- variable names from World of Warcraft Programming, page 529
  local numBatchAuctions, totalAuctions = GetNumAuctionItems("owner")
  if numBatchAuctions < totalAuctions then
    ChatFrame3:AddMessage('retrieved incomplete auction owner data')
    return
  end

  local new_auction_owned_counts = {}
  for i = 1,numBatchAuctions do
    -- variable names from World of Warcraft Programming, page 447
    local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo("owner", i)

    -- sold auctions do not have count associated with them
    if count > 0 then
      if new_auction_owned_counts[name] == nil then
        new_auction_owned_counts[name] = count
      else
        new_auction_owned_counts[name] = new_auction_owned_counts[name] + count
      end
    end
  end

  auction_owned_counts = new_auction_owned_counts
end

-- miscellaneous

function pretty_price(input_price)
  local max_multiple_price = input_price * 0.05
  local function compute_multiple(base_price)
    return base_price * math.pow(10, math.floor(math.log10(max_multiple_price / base_price)))
  end

  local multiple_price = max(compute_multiple(1), compute_multiple(25), compute_multiple(5))
  return multiple_price * math.floor(input_price / multiple_price)
end

function mst_bid(duration)
  if duration == nil then
    duration = 1440
  end

  if CursorHasItem() ~= nil then
    ClickAuctionSellItemButton()
  end

  -- variable names from World of Warcraft Programming, page 449
  local name, texture, count, quality, canUse, price = GetAuctionSellItemInfo()
  if name == nil then
    return
  end

  -- cache vendor unit price
  --MOON_SABER_TRADER_VENDOR_SELL_DATA[name] = price / count

  -- can't handle stacks yet
  if count > 1 then
    message('no stack support')
    return
  end

  local listing_id = listing_index[name]
  if listing_id == nil then

    local partial_ready, full_ready = CanSendAuctionQuery("list")
    if partial_ready then
      DEFAULT_CHAT_FRAME:AddMessage(format('MST querying AH for %s', name))
      QueryAuctionItems(name, 1, 100, nil, nil, nil, nil, nil, nil, false)
    end

    message(format('no listing data for %s', name))
    return
  end

  local self_minbid = listing_data['self_minbid'][listing_id]
  local self_buyout = listing_data['self_buyout'][listing_id]
  local self_flag = self_buyout ~= nil

  local comp_minbid = listing_data['competition_minbid'][listing_id]
  local comp_buyout = listing_data['competition_buyout'][listing_id]
  local comp_flag = comp_buyout ~= nil

  local new_minbid
  local new_buyout
  if self_flag then
    if comp_flag then
      -- we have competition. check if we are in lead.
      if self_buyout < comp_buyout then
        -- price at least as much as before, but move up under competition
        new_buyout = max(self_buyout, pretty_price(comp_buyout * 0.95))
      else
        new_buyout = pretty_price(comp_buyout * 0.95)
      end

      new_minbid = pretty_price(min(max(new_buyout * 0.9, comp_minbid), new_buyout * 0.95)) - 1
    else
      -- we don't have competition. carry on!
      new_minbid = self_minbid
      new_buyout = self_buyout
    end
  else
    if comp_flag then
      -- we aren't present. beat out competition.
      new_buyout = pretty_price(comp_buyout * 0.95)
      new_minbid = pretty_price(min(max(new_buyout * 0.9, comp_minbid), new_buyout * 0.95)) - 1
    else
      message('no current bids')
    end
  end

  if new_minbid == nil then
    return
  elseif new_minbid > new_buyout then
    message('calculated minbid greater than buyout')
    return
  end

  --ChatFrame3:AddMessage(format('Listing %s for %s - %s', name, format_price(new_minbid), format_price(new_buyout)))
  StartAuction(new_minbid, new_buyout, duration)
end

------------------------------------------------------------
-- listing -------------------------------------------------
------------------------------------------------------------

local listing_active = false
local listing_last_time = nil
local listing_pattern = nil

local function listing_scroll(value)
  local xPos, yPos = CombatText_StandardScroll(value)
  --return -256 * COMBAT_TEXT_X_SCALE, -128 * COMBAT_TEXT_Y_SCALE + yPos;
  --return xPos, -128 * COMBAT_TEXT_Y_SCALE + yPos;
  --return -256 * COMBAT_TEXT_X_SCALE, 256  value.scrollTime/COMBAT_TEXT_SCROLLSPEED
  return -256 * COMBAT_TEXT_X_SCALE, 128 + 128 * COMBAT_TEXT_Y_SCALE * value.scrollTime/COMBAT_TEXT_SCROLLSPEED;
end

local function listing_update()
  -- don't ping unless actively listing items
  if listing_active == false then
    return
  end

  -- only take action once per second
  local current_time = time()
  if current_time - listing_last_time < 2 then
    return
  end
  listing_last_time = current_time

  CombatText_AddMessage('Listing Update', listing_scroll, 1.0, 1.0, 0.0, nil, false)

  -- scan bag contents and classify interesting slots

  local empty_spots = {}
  local locked_spots = {}
  local single_spots = {}
  local stack_spots = {}

  for bag = 0,4 do
    for slot = 1,GetContainerNumSlots(bag) do
      local slot_desc = {bag, slot}

      local bag_item_link = GetContainerItemLink(bag, slot)
      if bag_item_link then
        local bag_item_name = get_item_name_from_item_link(bag_item_link)
        if string.find(bag_item_name:lower(), listing_pattern, 1, true) ~= nil then
          local _, bag_item_count, bag_item_locked = GetContainerItemInfo(bag, slot)
          if bag_item_locked == nil then
            if bag_item_count > 1 then
              table.insert(stack_spots, slot_desc)
            else
              table.insert(single_spots, slot_desc)
            end
          else
            table.insert(locked_spots, slot_desc)
          end
        end
      else
        table.insert(empty_spots, slot_desc)
      end
    end
  end

  -- randomize all lists of interesting inventory spots
  for _, t in pairs({empty_spots, locked_spots, single_spots, stack_spots}) do
    for i = 2,#t do
      local j = ceil(random() * i)
      if j < i then
        local temp = t[j]
        t[j] = t[i]
        t[i] = temp
      end
    end
  end

  -- split stacks into empty spots
  for i = 1,min(#empty_spots, #stack_spots) do
    local empty_bag = empty_spots[i][0]
    local empty_slot = empty_spots[i][1]

    ClearCursor()
    SplitContainerItem(stack_spots[i][1], stack_spots[i][2], 1)
    PickupContainerItem(empty_spots[i][1], empty_spots[i][2])
  end

  -- list single spots in AH
  for i = 1,#single_spots do
    ClearCursor()
    
    PickupContainerItem(single_spots[i][1], single_spots[i][2])
    mst_bid(2880)
  end

  -- stop if out of stuff to do
  if #locked_spots + #single_spots + #stack_spots <= 0 then
    listing_active = false
  end
end

-- setup updates while auction frame is visible. we have to wait for
-- the first time it is shown to setup the hook because AuctionFrame
-- does not exist at startup.

-- thus does not seem to be firing
event_handlers['AUCTION_HOUSE_CLOSE'] = function()
  print('AUCTION_HOUSE_CLOSE')
  listing_active = false
  listing_pattern = nil
end

event_handlers['AUCTION_HOUSE_SHOW'] = function()
  -- make sure listing state is clear
  listing_active = false

  -- only setup hook once
  if listing_last_time ~= nil then
    return
  end
  listing_last_time = 0

  AuctionFrame:HookScript("OnUpdate", listing_update)
end

------------------------------------------------------------
-- mail ----------------------------------------------------
------------------------------------------------------------

local mail_pop_active = false
local mail_pop_check_time = 0
local mail_pop_index = nil

local function mail_pop_check(index)
  local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, itemCount, wasRead, wasReturned, textCreated, canReply, isGM, itemQuantity = GetInboxHeaderInfo(index)    

  if sender == nil then
    return
  elseif not string.find(sender, 'Auction House') then
    return
  end

  if subject:find('^Auction expired:') ~= nil then
    return function() TakeInboxItem(index, 1) end
  end

  -- variable names from World of Warcraft Programming, page 498
  local bodyText, texture, isTakeable, isInvoice = GetInboxText(index)
  if isInvoice == nil then
    return
  end
  
  -- variable names from World of Warcraft Programming, page 499
  local invoiceType, itemName, playerName, bid, buyout, deposit, consignment, moneyDelay, etaHour, etaMin = GetInboxInvoiceInfo(index)

  if (invoiceType == 'buyer') or (invoiceType == 'seller') then
    return function() AutoLootMailItem(index) end
  end
end

local function mail_pop()
  local inbox_size = GetInboxNumItems()
  if inbox_size == nil then
    return
  end

  for index = inbox_size,1,-1 do
    local pop_func = mail_pop_check(index)
    if pop_func ~= nil then
      if index == mail_pop_index then
        return
      end

      mail_pop_index = index
      pop_func()
      return
    end
  end

  mail_pop_index = 0
end

event_handlers['MAIL_CLOSED'] = function()
  mail_pop_active = false
end

event_handlers['MAIL_INBOX_UPDATE'] = function ()
  if not mail_pop_active then
    return
  end

  mail_pop()
end

event_handlers['MAIL_SHOW'] = function()
  mail_pop_active = false
end

-- this hook is to ping the inbox to refresh its contents
InboxFrame:HookScript("OnUpdate", function()
  -- don't ping unless on active scan
  if not mail_pop_active then
    return
  end

  -- don't need to ping until we clear mail
  if mail_pop_index ~= 0 then
    return
  end

  -- only ping once every couple seconds
  local current_time = time()
  if current_time - mail_pop_check_time < 2 then
    return
  end

  mail_pop_check_time = current_time
  CombatText_AddMessage('Checking Inbox', CombatText_StandardScroll, 1.0, 1.0, 0.0)
  CheckInbox()
end)

------------------------------------------------------------
-- market functions ----------------------------------------
------------------------------------------------------------

local market_snapshot = {}

function mst_market_prices(filter, max_owned)
  if type(filter) == 'string' then
    filter = {filter}
  end

  local price_data = {}
  for item_name, listing_id in pairs(listing_index) do
    local item_match = nil
    for _, f in ipairs(filter) do
      if string.find(item_name:lower(), f:lower(), 1, true) ~= nil then
        item_match = true
      end
    end

    if item_match then
      local competition_buyout = listing_data['competition_buyout'][listing_id]
      local self_buyout = listing_data['self_buyout'][listing_id]
      if competition_buyout ~= nil then
        price_data[item_name] = competition_buyout
      elseif self_buyout ~= nil then
        price_data[item_name] = self_buyout
      end
    end
  end

  local items_by_price = {}
  for item_name, item_price in pairs(price_data) do
    table.insert(items_by_price, item_name)
  end
  table.sort(items_by_price, function (a,b) return price_data[a] > price_data[b] end)

  market_snapshot = {}
  for i, item_name in ipairs(items_by_price) do
    local item_price = price_data[item_name]
    local item_owned_count = auction_owned_counts[item_name]

    if item_owned_count == nil then
      table.insert(market_snapshot, {name = item_name, buyout_price = item_price, owned_count = 0})
    elseif max_owned == nil then
      table.insert(market_snapshot, {name = item_name, buyout_price = item_price, owned_count = item_owned_count})
    elseif item_owned_count <= max_owned then
      table.insert(market_snapshot, {name = item_name, buyout_price = item_price, owned_count = item_owned_count})
    end
  end

  MoonSaberTrader_Update()
  MoonSaberTraderFrame:Show()
end

function mst_mill_stats(input_name)
  local transaction_log = MoonSaberLedger_CharacterData['transaction_log']

  local input_count = 0
  local output_counts = {}
  for i = 1,#transaction_log do
    local t = transaction_log[i]
    if t['type'] == 'refine' and t['input_name'] == input_name then
      input_count = input_count + 1

      for j = 1,5 do
        local output_name_field
        local output_count_field
        if j == 1 then
          output_name_field = 'output_name'
          output_count_field = 'output_count'
        else
          output_name_field = 'output' .. j .. '_name'
          output_count_field = 'output' .. j .. '_count'
        end
        
        local output_name = t[output_name_field]
        if output_name ~= nil then
          if output_counts[output_name] == nil then
            output_counts[output_name] = 0
          end
          output_counts[output_name] = output_counts[output_name] + t[output_count_field]
        end
      end
    end
  end
  print(format('refined %s %d times', input_name, input_count))
  for output_name, output_count in pairs(output_counts) do
    print(format('  got %d %s (%.2f/attempt)', output_count, output_name, output_count / input_count))
  end
end

function mst_recent_sales(s)
  local transaction_log = MoonSaberLedger_CharacterData['transaction_log']
  local recent_time = time() - s

  local sale_data = {}
  for i = 1,#transaction_log do
    local t = transaction_log[i]
    if t.time >= recent_time then
      if t['type'] == 'sell' then
        local item_name = t.input_name
        if sale_data[item_name] == nil then
          sale_data[item_name] = {count = 0, price = 0}
        end

        sale_data[item_name].count = sale_data[item_name].count + t.input_count
        sale_data[item_name].price = sale_data[item_name].price + t.sell_price
      end
    end
  end

  local items_by_volume = {}
  for item_name, item_data in pairs(sale_data) do
    table.insert(items_by_volume, item_name)
  end
  table.sort(items_by_volume, function (a,b) return sale_data[a].price < sale_data[b].price end)

  ChatFrame3:AddMessage('RECENT AH SALES START')
  for i, item_name in ipairs(items_by_volume) do
    local item_data = sale_data[item_name]
    ChatFrame3:AddMessage(format('%s => %d at %.2f', item_name, item_data.count, item_data.price / item_data.count / 10000))
  end
  ChatFrame3:AddMessage('RECENT AH SALES END')
end

function mst_split(f, s)

  -- find a matching item

  local match_count = 0
  local match_bag
  local match_slot

  for bag = 0,4 do
    for slot = 1,GetContainerNumSlots(bag) do
      local bag_item_link = GetContainerItemLink(bag, slot)
      if bag_item_link then
        local bag_item_name = get_item_name_from_item_link(bag_item_link)
        if string.find(bag_item_name:lower(), f:lower(), 1, true) ~= nil then
          local _, bag_item_count, bag_item_locked = GetContainerItemInfo(bag, slot)
          if (bag_item_count > s) and (bag_item_locked == nil) then
            match_count = match_count + 1
            if random() * match_count < 1 then
              match_bag = bag
              match_slot = slot
            end
          end
        end
      end
    end
  end

  if match_bag == nil then
    return
  end

  SplitContainerItem(match_bag, match_slot, s)

  -- find an empty slot

  local empty_count = 0
  local empty_bag
  local empty_slot

  for bag = 0,4 do
    for slot = 1,GetContainerNumSlots(bag) do
      local bag_item_link = GetContainerItemLink(bag, slot)
      if bag_item_link == nil then
        empty_count = empty_count + 1
        if random() * empty_count < 1 then
          empty_bag = bag
          empty_slot = slot
        end
      end
    end
  end

  if empty_bag ~= nil then
    PickupContainerItem(empty_bag, empty_slot)
    return
  end

  ChatFrame3:AddMessage('no empty slots found')
  ClearCursor()
end

-- window handlers

function MoonSaberTrader_Update()
  MoonSaberTraderFrameTitleText:SetText("Market Statistics")
  MoonSaberTraderMarket_Update(MoonSaberTraderMarketFrame)
end

function MoonSaberTraderMarket_Update(self)
  local market_size
  if market_snapshot == nil then
    market_size = 0
  else
    market_size = #market_snapshot
  end

  local itemOffset = FauxScrollFrame_GetOffset(MoonSaberTraderMarketFrameScrollFrame)
  for i=1,MARKET_ROWS do
    local item_index = i + itemOffset
    if item_index <= market_size then
      item_data = market_snapshot[item_index]
      getglobal("MoonSaberTraderMarketFrameRow" .. i .. "ItemName"):SetText(item_data.name)

      local pretty_buyout_price = ceil(item_data.buyout_price)
      for coin=1,3 do
        local coin_value = math.pow(100, coin)
        if pretty_buyout_price >= 100 * coin_value then
     	  pretty_buyout_price = floor(pretty_buyout_price / coin_value) * coin_value
        end
      end
      MoneyFrame_Update(getglobal("MoonSaberTraderMarketFrameRow" .. i .. "BuyoutPrice"), pretty_buyout_price)
      getglobal("MoonSaberTraderMarketFrameRow" .. i .. "Listed"):SetText("" .. item_data.owned_count)
      getglobal("MoonSaberTraderMarketFrameRow" .. i):Show()
    else
      getglobal("MoonSaberTraderMarketFrameRow" .. i):Hide()      
    end    
  end

  FauxScrollFrame_Update(MoonSaberTraderMarketFrameScrollFrame, market_size, MARKET_ROWS, 20);
end

-- event handling

event_handlers['AUCTION_ITEM_LIST_UPDATE'] = auction_item_list_update
event_handlers['AUCTION_OWNED_LIST_UPDATE'] = auction_owned_list_update

--event_handlers['MERCHANT_UPDATE'] = merchant_update
--event_handlers['TRADE_SKILL_UPDATE'] = trade_skill_update
--event_handlers['VARIABLES_LOADED'] = setup_variables

local frame = CreateFrame("Frame", "MoonSaberTraderEventFrame")
frame:SetScript("OnEvent", 
                function (self, event, ...)
                  if type(event_handlers[event]) == 'function' then
                    event_handlers[event](self, event, ...)
                  end
                end)

for k, v in pairs(event_handlers) do
  frame:RegisterEvent(k)
end

-- slash commands

local function mst_usage()
  print('usage: /mst <command> <args>')
  print('')
  print('       /mst help          (show this help)')
  print('       /mst list <pattern>')
  print('                          (list matching items in AH. minimum pattern length 5)')
  print('       /mst mail          (read and collect items/gold from AH mail)')
  print('       /mst market <comma separated items>')
  print('                          (show lowest buyout prices for specified items)')
  print('       /mst scan          (scan all AH prices)')
end

local function trim(s)
  s2 = s:match("^ *([^ ].*)$")
  if s2 ~= nil then
    s = s2
  end

  s2 = s:match("^([^ ].*) *$")
  if s2 ~= nil then
    s = s2
  end

  return s
end

local function mst_dispatch(dispatch_table, command)
  -- various details from World of Warcraft Programming, page 261-262

  local subcommand, args = command:match("([^ ]+) (.+)")
  if subcommand == nil then
    subcommand = command
    args = nil
  end

  if dispatch_table[subcommand] == nil then
    mst_usage()
    return
  end

  dispatch_table[subcommand](args)
end

local mst_commands = {}

mst_commands['help'] = mst_usage

mst_commands['list'] = function(args)
  listing_active = false

  if args == nil then
    mst_usage()
    return
  end

  local pattern = trim(args)
  if strlen(pattern) < 5 then
    mst_usage()
    return
  end
  
  listing_pattern = pattern:lower()
  listing_active = true
end

mst_commands['mail'] = function(args)
  local inbox_size = GetInboxNumItems()
  if (inbox_size == nil) or (inbox_size == 0) then
    print('MoonSaberTrader: no mail found')
    return
  end

  mail_pop_active = true
  mail_pop_index = nil
  mail_pop()
end

mst_commands['market'] = function(args)
  if args == nil then
    mst_usage()
    return
  end

  local patterns = {}
  local pattern
  while args:match(",") do
    pattern, args = args:match("([^,]*),(.*)")
    if pattern ~= nil and pattern ~= "" then
      table.insert(patterns, trim(pattern))
    end
  end
  if args ~= "" then
    table.insert(patterns, trim(args))
  end

  mst_market_prices(patterns, 4)
end

mst_commands['scan'] = function(args)
  local partial_ready, full_ready = CanSendAuctionQuery("list")
  if not full_ready then
    print('MoonSaberTrader: Full AH scan still on cooldown.')
    return
  end

  QueryAuctionItems("of", 1, 100, nil, 5, 5, 2, nil, nil, true)
end

SLASH_MST1 = "/mst"
SlashCmdList["MST"] = function(command)
  mst_dispatch(mst_commands, command)
end

